#pragma once
#include "rulebase2b.h"
#include<assert.h>
#include <iostream>

extern ofstream logFile;

class FreeFormRule
{
protected:
	int** history; //2D array, feature[i][j] is the feature J of the history i
	int historySteps;
	int featuresPerHistory;
	int label;

public:

	FreeFormRule (const FreeFormRule& other): historySteps(other.historySteps), featuresPerHistory(other.featuresPerHistory), label(other.label)
	{
		history = new int*[historySteps];
		for (int i = 0; i < historySteps; ++i)
			history[i] = new int[featuresPerHistory];
		
		for (int historyIndex = 0; historyIndex < historySteps; historyIndex++)
			for (int featureIndex = 0; featureIndex < featuresPerHistory; featureIndex++)
				history[historyIndex][featureIndex] = other.history[historyIndex][featureIndex];
	}


	/** This constructor is provided with the important features and copies only the important features of each time step 
	 * in to the new object.
	 */
	FreeFormRule (const LongInstance2B& LI2B, int* importantIndices, int importantSize) : featuresPerHistory(importantSize), label(LI2B.getLabel()), historySteps(LI2B.neighborhoodNum())
	{
		history = new int*[historySteps];
		for (int i = 0; i < historySteps; ++i)
			history[i] = new int[featuresPerHistory];
		
		try
		{
			for (int historyIndex = 0; historyIndex < historySteps; historyIndex++)
				for (int featureIndex = 0; featureIndex < featuresPerHistory; featureIndex++)
					history[historyIndex][featureIndex] = LI2B.getFeature(historyIndex, importantIndices[featureIndex]);
		}
		catch (exception e)
		{
			std::cout << "ERROR: " << e.what();
		}
	}

	/** Constructor for test purposes. We can use this to manually build FreeFormRule objects.
	 *  \param features string of 0s and 1s, separated by comma (,)s. e.g. "101,011,110"
	 */
	FreeFormRule (string features, int label)
	{
		int sCounter = 0;
		//finding the chunks of histories
		int thisLocation = 0;
		vector<int> commaLocation;
		do
		{
		thisLocation = features.find(',', sCounter);
		if (thisLocation != string::npos)
			commaLocation.push_back(thisLocation);
		sCounter = thisLocation+1;		
		}		
		while (thisLocation != string::npos);
		commaLocation.push_back(features.size()); //we add a virtual comma to the end of the string
		//now we have the location of commas, let's check the size of the chunks
		int chunkSize = commaLocation[0];
		for (int i = 1; i < (int)commaLocation.size(); ++i)
			if ( commaLocation[i] - commaLocation[i-1] - 1 != chunkSize )
			{
				cerr << "The string chunks you provided for constructing the FreeFormRule object do not have the same sizes.";
				break;
			}
		//now we are sure that all the string chunks have the same length. let's build the history steps.

		historySteps = commaLocation.size();
		featuresPerHistory = chunkSize;
		history = new int*[historySteps];
		for (int hIndex = 0; hIndex < historySteps; ++hIndex)
			history[hIndex] = new int[featuresPerHistory];

		for (int hIndex = 0; hIndex < historySteps; ++hIndex)
			for (int fIndex = 0 ; fIndex < featuresPerHistory; ++fIndex)
			{
				int linearIndex = hIndex * (featuresPerHistory + 1 /*1 for comma*/) + fIndex;
				history[hIndex][fIndex] = features.at(linearIndex) - 48; //'0'=48, '1'=49, etc.
			}
		this->label = label;
	}

	/** This constructor copies all the info in the LI2B into the new object, no compression. */
	FreeFormRule (const LongInstance2B& LI2B) : label (LI2B.getLabel())
	{
		historySteps = LI2B.neighborhoodNum();
		featuresPerHistory = LI2B.getNeighborhood2Size();
		history = new int*[historySteps];
		for (int i = 0; i < historySteps; ++i)
			history[i] = new int[featuresPerHistory];
		
		for (int historyIndex = 0; historyIndex < historySteps; historyIndex++)
			for (int featureIndex = 0; featureIndex < featuresPerHistory; featureIndex++)
				history[historyIndex][featureIndex] = LI2B.getFeature(historyIndex, featureIndex);
	}

	FreeFormRule(int historySteps, int featuresPerHistory, int label = 1) : historySteps(historySteps), featuresPerHistory(featuresPerHistory), label(label)
	{
		history = new int*[historySteps];
		for (int i = 0; i < historySteps; ++i)
			history[i] = new int[featuresPerHistory];
	};

	FreeFormRule* removeFeatureAndMakeNew (int feature4Removal) const
	{
		if (feature4Removal >= featuresPerHistory)
		{
			assert(feature4Removal >= featuresPerHistory);
			cout << "Error: Feature out of limit. FreeFormRule& removeFeature (int feature4Removal)";
		}
		FreeFormRule* result = new FreeFormRule(historySteps,featuresPerHistory - 1) ;
		//result.historySteps = this->historySteps;
		//result.featuresPerHistory = this->featuresPerHistory - 1;
		for (int historyIndex = 0; historyIndex < historySteps; historyIndex++)
		{
				for (int featureIndex = 0; featureIndex < feature4Removal; featureIndex++)
					result->history[historyIndex][featureIndex] = this->history[historyIndex][featureIndex];
				for (int featureIndex = feature4Removal+1; featureIndex < featuresPerHistory; featureIndex++)
					result->history[historyIndex][featureIndex-1] = this->history[historyIndex][featureIndex];
		}
		return result;
	}

	void removeFeature (int feature4Removal)
	{
		//LOGLINE("before removal:");
		//LOGLINE(*this);
		if (feature4Removal >= featuresPerHistory)
		{
			assert(feature4Removal >= featuresPerHistory);
			cout << "Error: Feature out of limit. FreeFormRule& removeFeature (int feature4Removal)";
		}
		//FreeFormRule* result = new FreeFormRule(historySteps,featuresPerHistory - 1) ;
		//result.historySteps = this->historySteps;
		//result.featuresPerHistory = this->featuresPerHistory - 1;
		int** newHistory = new int*[historySteps];
		for (int historyIndex = 0; historyIndex < historySteps; ++historyIndex)
		{
			newHistory[historyIndex] = new int[featuresPerHistory-1];
			for (int featureIndex = 0; featureIndex < feature4Removal; featureIndex++)
				newHistory[historyIndex][featureIndex] = history[historyIndex][featureIndex];
			for (int featureIndex = feature4Removal+1; featureIndex < featuresPerHistory; featureIndex++)
				newHistory[historyIndex][featureIndex-1] = history[historyIndex][featureIndex];
			delete[] history[historyIndex];
		}
		delete[] history;
		history = newHistory;
		featuresPerHistory--;
		//LOGLINE("after removal:");
		//LOGLINE(*this);
		//LOGLINE(endl);
	}

	int getLabel () const {return label;}
	int getFeature (int featureInHistory, int historyIndex = 0) const 
	{
		assert (featureInHistory < featuresPerHistory &&  historyIndex < historySteps && "Out of array boundry");
		return history[historyIndex][featureInHistory];
	}
	int getHistorySize () const {return historySteps;}
	int getFeaturesPerHistory () const {return featuresPerHistory;}

	~FreeFormRule()
	{
		for (int i = 0; i < historySteps; ++i)
			delete[] history[i];
		delete[] history;
	}

	bool conflicts (const FreeFormRule& other) const
	{
		for (int h = 0; h < this->historySteps; ++h)
			for (int f = 0; f < this->featuresPerHistory; ++f)
				if (other.history[h][f] != history[h][f])
					return false;
		//at this point the IF parts are the same
		if (other.label != this->label)
			return true;
		return false;
	}

	void addFeature(int* featuresToBeAdded, int layersNum)
	{
		//int** history; //2D array, feature[i][j] is the feature J of the history i
		//int historySteps;
		//int featuresPerHistory;
		//int label;
		assert (historySteps == layersNum);
		int** newHistory = new int* [layersNum];
		for (int i = 0; i < layersNum; ++i)
		{
			newHistory[i] = new int[featuresPerHistory+1];
			for (int j = 0; j < featuresPerHistory; ++j)
				newHistory[i][j] = history[i][j];
			newHistory[i][featuresPerHistory] = featuresToBeAdded[i];
			delete[] history[i];
		}
		delete[] history;
		history = newHistory;
		++featuresPerHistory;
	}

	FreeFormRule& operator= (const FreeFormRule& other)
	{
		historySteps = other.historySteps;
		featuresPerHistory = other.featuresPerHistory;
		label = other.label;

		history = new int*[historySteps];
		for (int i = 0; i < historySteps; ++i)
			history[i] = new int[featuresPerHistory];
		
		for (int historyIndex = 0; historyIndex < historySteps; historyIndex++)
			for (int featureIndex = 0; featureIndex < featuresPerHistory; featureIndex++)
				history[historyIndex][featureIndex] = other.history[historyIndex][featureIndex];
		return *this;
	}

	bool operator== (const FreeFormRule& other) const
	{
		for (int h = 0; h < this->historySteps; ++h)
			for (int f = 0; f < this->featuresPerHistory; ++f)
				if (other.history[h][f] != history[h][f])
					return false;
		//at this point the IF parts are the same
		if (other.label != this->label)
			return false;
		return true;
	}

	friend ostream& operator<< (ostream& stream, const FreeFormRule& ffr) 
	{
		for (int i = 0; i < ffr.historySteps; ++i)
		{
			stream << "(";
			for (int j = 0; j < ffr.featuresPerHistory; ++j)
				stream << ffr.history[i][j];
			stream << ")";
		}
		stream << " -> " << ffr.label;
		return stream;
	}

	friend bool ffrLabelIsZero (const FreeFormRule& ffr)
	{
		return (ffr.label == 0);
	}

	
	
};
